﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AnnotateMe.Logic
{
    /// <summary>
    /// Parses caret-delimited text query string into pairs (label -> annotation).
    /// Looks for patterns of type (LABEL)^(ANNOTATION).
    /// Accepts '\' as escape symbol. The following are all accepted escape-sequences: "\\", "\^", "\(", "\)".
    /// Example:  "find (pizza hut)^(business name)" parsed into "find" and "pizza hut" annotated with "business name"
    /// </summary>
    public class CaretDelimitedQueryParser
    {
        private class Partitioning
        {
            public string before, label, annotation, after;
        }

        public IEnumerable<Tuple<string, string>> Parse(string query)
        {
            if (string.IsNullOrEmpty(query))
            {
                return Enumerable.Empty<Tuple<string, string>>();
            }

            var partitioning = FindValidPartitioning(query);

            if (partitioning == null)
            {
                return new[] { new Tuple<string, string>(UnescapeToken(query), string.Empty) };
            }
            else
            {
                return Parse(partitioning.before)
                    .Concat(new[] { new Tuple<string, string>(UnescapeToken(partitioning.label), UnescapeToken(partitioning.annotation)) })
                    .Concat(Parse(partitioning.after));
            }
        }

        private Partitioning FindValidPartitioning(string query)
        {
            const string CaretPattern = ")^(";
            int index = query.IndexOf(CaretPattern);
            while (index >= 0)
            {
                int openingBracketIndex = -1;
                for (int i = index; i >= 0; i--)
                {
                    if (query[i] == '(' && !IsSymbolEscaped(query, i))
                    {
                        openingBracketIndex = i;
                        break;
                    }
                }

                if (openingBracketIndex != -1)
                {
                    int closingBracketIndex = -1;
                    for (int i = index + 1; i < query.Length; i++)
                    {
                        if (query[i] == ')' && !IsSymbolEscaped(query, i))
                        {
                            closingBracketIndex = i;
                            break;
                        }
                    }

                    if (closingBracketIndex != -1)
                    {
                        return new Partitioning
                        {
                            before = query.Substring(0, openingBracketIndex),
                            label = query.Substring(openingBracketIndex + 1, index - openingBracketIndex - 1),
                            annotation = query.Substring(index + 3, closingBracketIndex - index - 3),
                            after = query.Substring(closingBracketIndex + 1)
                        };
                    }
                }

                index = query.IndexOf(CaretPattern, index + 1);
            }

            return null;
        }

        private bool IsSymbolEscaped(string query, int index)
        {
            int numescapes = 0;
            for (int i = index -1; i >= 0; i--)
            {
                if (query[i] == '\\')
                    numescapes++;
                else
                    break;
            }

            return (numescapes % 2 == 1);
        }

        private string UnescapeToken(string token)
        {
            if (token.Length < 2)
                return token;

            StringBuilder result = new StringBuilder(token.Length);

            char? previous = null;

            for (int i = 0; i < token.Length; i++)
            {
                if (previous == null)
                {
                    previous = token[i];
                    continue;
                }

                if (previous != '\\')
                {
                    result.Append(previous);
                    previous = token[i];
                    continue;
                }
                else
                {
                    if (token[i] == '\\' || token[i] == '(' || token[i] == ')' || token[i] == '^')
                    {
                        result.Append(token[i]);
                    }
                    else
                    {
                        result.Append(previous);
                        result.Append(token[i]);
                    }
                    previous = null;
                    continue;
                }
            }

            if (previous != null)
            {
                result.Append(previous);
            }

            return result.ToString();
        }
    }
}
